﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Http;
using CkSoftware.GroupMe.Sdk.Core.Dao;
using CkSoftware.GroupMe.Sdk.Core.Exceptions;
using CkSoftware.GroupMeChatModule.Components;
using CkSoftware.GroupMeChatModule.Managers;
using DotNetNuke.Security;
using DotNetNuke.Services.Exceptions;
using DotNetNuke.Web.Api;

namespace CkSoftware.GroupMeChatModule.WebApi
{
	[SupportedModules("CkSoftwareGroupMeChatModule")]
	public class MessagesController : DnnApiController
	{
		private static readonly ConcurrentDictionary<int, GroupManager> GroupManagers = new ConcurrentDictionary<int, GroupManager>();
		
		[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
		[HttpGet]
		public async Task<object> IsAuthorized()
		{
			var groupManager = GetCurrentGroupManager();
			return await groupManager.GetAuthorizationStatus(UserInfo.UserID, SessionIdProvider.GetSessionId());
		}

		[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
		[HttpGet]
		public async Task<IEnumerable<GroupMeMessageData>> GetMessages(string newestMessageId)
		{
			var groupManager = GetCurrentGroupManager();

			if (!groupManager.AuthManager.HaveAuthTokenForUser(UserInfo.UserID) && !groupManager.AuthManager.HaveAuthTokenForAnonymous(SessionIdProvider.GetSessionId()))
			{
				throw new UnauthorizedAccessException("You must be logged for GroupMe to use this service. Please refresh the page.");
			}

			var messages = await groupManager.GetMessagesObject();

			if (!string.IsNullOrWhiteSpace(newestMessageId))
			{
				return await GetMessagesNewerThanMessageId(newestMessageId);
			}

			return messages.LoadedMessages.OrderByDescending(message => message.CreatedAt).Take(20);
		}

		private async Task<IEnumerable<GroupMeMessageData>> GetMessagesNewerThanMessageId(string newestMessageId)
		{
			var groupManager = GetCurrentGroupManager();
			
			if (!groupManager.AuthManager.HaveAuthTokenForUser(UserInfo.UserID) && !groupManager.AuthManager.HaveAuthTokenForAnonymous(SessionIdProvider.GetSessionId()))
			{
				throw new UnauthorizedAccessException("You must be logged for GroupMe to use this service. Please refresh the page.");
			}
			
			var messages = await groupManager.GetMessagesObject();
			var loadedMessages = messages.LoadedMessages.OrderBy(message => message.CreatedAt).ToList();

			var clientNewestMessage = loadedMessages.SingleOrDefault(m => m.MessageId == newestMessageId);
			var clientNewestMessageIndex = loadedMessages.IndexOf(clientNewestMessage);

			return loadedMessages.Skip(clientNewestMessageIndex + 1).Take(20);
		}

		[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
		[HttpGet]
		public async Task<IEnumerable<GroupMeMessageData>> GetOldMessages(string oldestMessageId)
		{
			var groupManager = GetCurrentGroupManager();

			if (!groupManager.AuthManager.HaveAuthTokenForUser(UserInfo.UserID) && !groupManager.AuthManager.HaveAuthTokenForAnonymous(SessionIdProvider.GetSessionId()))
			{
				throw new UnauthorizedAccessException("You must be logged for GroupMe to use this service. Please refresh the page.");
			}

			return await GetMessagesOlderThanMessageId(oldestMessageId);
		}

		private async Task<IEnumerable<GroupMeMessageData>> GetMessagesOlderThanMessageId(string oldestMessageId)
		{
			var groupManager = GetCurrentGroupManager();

			if (!groupManager.AuthManager.HaveAuthTokenForUser(UserInfo.UserID) && !groupManager.AuthManager.HaveAuthTokenForAnonymous(SessionIdProvider.GetSessionId()))
			{
				throw new UnauthorizedAccessException("You must be logged for GroupMe to use this service. Please refresh the page.");
			}

			var messages = await groupManager.GetMessagesObject();

			var loadedMessages = messages.LoadedMessages.OrderByDescending(message => message.CreatedAt).ToList();

			var clientOldestMessage = loadedMessages.SingleOrDefault(m => m.MessageId == oldestMessageId);
			var clientOldestMessageIndex = loadedMessages.IndexOf(clientOldestMessage);

			if (clientOldestMessageIndex == loadedMessages.Count - 1)
			{
				try
				{
					await messages.ScrollBackward();
					groupManager.ProcessGroupMeMessage(messages.LoadedMessages);

					loadedMessages = messages.LoadedMessages.OrderByDescending(message => message.CreatedAt).ToList();
				}
				catch (GroupMeException ex)
				{
					Exceptions.LogException(ex);
					throw;
				}
			}
			
			return loadedMessages.Skip(clientOldestMessageIndex + 1).Take(20);
		}

		[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
		[HttpPost]
		public async Task PostMessage([FromBody] string message)
		{
			var groupManager = GetCurrentGroupManager();

			if (!groupManager.AuthManager.HaveAuthTokenForUser(UserInfo.UserID) && !groupManager.AuthManager.HaveAuthTokenForAnonymous(SessionIdProvider.GetSessionId()))
			{
				throw new UnauthorizedAccessException("You must be logged for GroupMe to use this service. Please refresh the page.");
			}

			await groupManager.PostMessage(UserInfo.UserID, SessionIdProvider.GetSessionId(), message);
		}

		[DnnModuleAuthorize(AccessLevel = SecurityAccessLevel.View)]
		[HttpPut]
		public void ChangePermanentLogin([FromBody]bool isPermanent)
		{
			var groupManager = GetCurrentGroupManager();

			if (UserInfo.UserID <= 0)
			{
				throw new UnauthorizedAccessException("You must be authenticated as Site user to use this service.");
			}

			if (!groupManager.AuthManager.HaveAuthTokenForUser(UserInfo.UserID))
			{
				throw new UnauthorizedAccessException("You must be logged for GroupMe to use this service. Please refresh the page.");
			}

			var authToken = groupManager.AuthManager.GetAuthTokenForUser(UserInfo.UserID);
			var permanentLoginController = new PermanentGroupMeLogonsController();
			var entry = permanentLoginController.GetItem(UserInfo.UserID);

			if (isPermanent)
			{
				if (entry != null)
				{
					entry.GroupMeAccessToken = authToken;
					entry.LastModifiedOnDate = DateTime.Now;

					permanentLoginController.UpdateItem(entry);
				}
				else
				{
					permanentLoginController.CreateItem(new PermanentGroupMeLogon
					{
						GroupMeAccessToken = authToken,
						UserId = UserInfo.UserID,
						LastModifiedOnDate = DateTime.Now
					});
				}
			}
			else
			{
				if (entry != null)
				{
					permanentLoginController.DeleteItem(entry);
				}
			}
		}

		private GroupManager GetCurrentGroupManager()
		{
			return GroupManagers.GetOrAdd(ActiveModule.ModuleID, id => new GroupManager(id));
		}
	}
}